#pragma once

#include "sfc/core/str.h"

namespace sfc::fmt {

struct Style {
  char _fill = 0;
  char _align = 0;   // [<>^=]
  char _sign = 0;    // [+- ]
  char _prefix = 0;  // [#]
  char _point = 0;   // [.]
  char _type = 0;    // [*]

  u8 _width = 0;
  u8 _precision = 0;

 public:
  // [[fill]align][sign]['#'][0][width][.][precision][type]
  static auto from_str(str::Str s) -> option::Option<Style>;

  [[sfc_inline]] auto fill() const -> char {
    return _fill ?: ' ';
  }

  [[sfc_inline]] auto align() const -> char {
    return _align;
  }

  [[sfc_inline]] auto sign(bool is_neg) const -> str::Str {
    if (is_neg) {
      return "-";
    }
    switch (_sign) {
      case '+': return "+";
      case '-': return " ";
      default: return "";
    }
    return "";
  }

  [[sfc_inline]] auto type() const -> char {
    return _type;
  }

  [[sfc_inline]] auto width() const -> u32 {
    return _width;
  }

  [[sfc_inline]] auto precision(u32 default_val) const -> u32 {
    return _point ? _precision : default_val;
  }
};

template <class... T>
struct Args {
  str::Str _pats = {};
  tuple::Tuple<const T*...> _args = {};

 public:
  Args(const auto& pats, const T&... args) noexcept : _pats{pats}, _args{&args...} {}

  void fmt(auto& f) const {
    if (sizeof...(T) == 1 && _pats == "{}") {
      f.write(*_args._0);
      return;
    }

    auto pats = _pats;
    _args.map([&](auto& p) {
      const auto i0 = pats.find('{').unwrap();
      f.write_str(pats[{0, i0}]);
      pats = pats[{i0 + 1, _}];

      const auto i1 = pats.find('}').unwrap();
      const auto ss = pats[{0, i1}];
      f.set_style(Style::from_str(ss).unwrap());
      pats = pats[{i1 + 1, _}];

      f.write(*p);
    });
    f.write_str(pats);
  }
};

template <>
struct Args<> {
  str::Str _pats = {};

 public:
  Args(const auto& pats) noexcept : _pats{pats} {}

  void fmt(auto& f) const {
    f.write_str(_pats);
  }
};

}  // namespace sfc::fmt
